﻿/*
 *  Source code courtesy of the desktopWeb.CodePlex.com community project. See MS-PL license on Codeplex.com. 
 *  This is beta code not intended for a production application.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.ComponentModel;
using System.Collections.ObjectModel;
using Microsoft.SharePoint.Client;
using System.IO;
using ClientOM = Microsoft.SharePoint.Client;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml;
using System.Activities.Tracking;
using System.Net;
using DocumentFormat.OpenXml.Wordprocessing;
using MyData.ExpressionBuilder.Model;

namespace MyData.Activities.OfficeXml
{
  [Designer(typeof(MyData.Activities.OfficeXml.DocumentPartsDesigner))]
  public sealed class DocumentParts : SharePointBaseActivity
  {
    [Browsable(false)]
    public Collection<Activity> Activities { get; set; }
    public InArgument<string> LibraryTitle { get; set; }
    public InArgument<string> DocumentName { get; set; }

    //SharePoint MetaData that other activities might be interested in
    public OutArgument<Dictionary<string, object>> ArgFileMetaData { get; set; }
    public OutArgument<FileInformation> ArgFileInformation { get; set; }

    //Content parts in this document. Content parts refer to Word ContentControls and Excel Ranges
    public OutArgument<Dictionary<string, string>> ContentParts { get; set; }
    
    //Activity variable for composite
    private Variable<Int32> activityIndex =  new Variable<int>("ActivityIndex", 0);
    private Stream fileStream = null;
    private Dictionary<string, string> contentParts = null;
    private string documentName = string.Empty;

    public DocumentParts()
    {
      Activities = new Collection<Activity>();
    }

    //Cache composite activity metadata
    protected override void CacheMetadata(NativeActivityMetadata metadata)
    {
      metadata.SetChildrenCollection(Activities);
      metadata.AddImplementationVariable(activityIndex);

      base.CacheMetadata(metadata);
    }

    protected override void Execute(NativeActivityContext context)
    {
      string url = base.Url.Expression.ToString();
      string libraryTitle = this.LibraryTitle.Expression.ToString();
      documentName = this.DocumentName.Expression.ToString();

      Dictionary<string, object> fileMetadata = new Dictionary<string, object>();
      FileInformation fileInformation = null;

      //Use the passed credentials
      if (this.NetworkCredential != null)
      {
        using(ClientContext clientContext = new ClientContext(url))
        {
          clientContext.Credentials = NetworkCredential.Get(context);

          Microsoft.SharePoint.Client.List sharedDocumentsList = clientContext.Web.Lists.GetByTitle(libraryTitle);

          //Next: CAMLBuilder
          CamlQuery camlQuery = new CamlQuery();
          camlQuery.ViewXml =
              @"<View>
                  <Query>
                    <Where>
                      <Eq>
                        <FieldRef Name='FileLeafRef'/>
                        <Value Type='Text'>" + documentName + @"</Value>
                      </Eq>
                    </Where>
                    <RowLimit>1</RowLimit>
                  </Query>
                </View>";
          Microsoft.SharePoint.Client.ListItemCollection listItems = sharedDocumentsList.GetItems(camlQuery);
          clientContext.Load(sharedDocumentsList);
          clientContext.Load(listItems);
          clientContext.ExecuteQuery();

          if (listItems.Count == 1)
          {
            ClientOM.ListItem item = listItems[0];

            fileMetadata.Add("FileRef", item["FileRef"]);
            fileMetadata.Add("FileType", item["File_x0020_Type"]);
            fileInformation = ClientOM.File.OpenBinaryDirect(clientContext, (string)item["FileRef"]);

            //Get the document stream if this Activity has ContentPart activities
            if (this.Activities.Count > 0)
            {
              fileStream = fileInformation.Stream;
            }
          }

          if (this.Activities.Count > 0)
          {
            this.FillParts();
          }

          //Set OutArguments
          context.SetValue(this.ArgFileInformation, fileInformation);
          context.SetValue(this.ArgFileMetaData, fileMetadata);
          context.SetValue(this.ContentParts, contentParts);
      }
      }
      else
      {
        //NOTE: Throw exception in production application
      }

    }


    private void FillParts()
    {
      string reference = string.Empty;
      string namedRange = string.Empty;
      string value = string.Empty;
     
      contentParts = new Dictionary<string, string>();

      if (fileStream != null)
      {
        using (MemoryStream memoryStream = new MemoryStream())
        {
          Common.CopyStream(fileStream, memoryStream);

          //Determine Word or Excel by extension
          if (Path.GetExtension(documentName) == ".docx")
            this.FillWordParts(memoryStream, namedRange, reference);
          else if (Path.GetExtension(documentName) == ".xlsx")
            this.FillCellParts(memoryStream, namedRange, reference);
        }
      }
    }

    //Example: Word Content Controls LINQ
    private void FillWordParts(MemoryStream memoryStream, string namedRange, string reference)
    {
      string value = string.Empty;

      using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(memoryStream, false))
      {
        //Add ContentParts dropped onto DocumentParts
        foreach (Activity a in this.Activities)
        {
          ContentPart part = (ContentPart)a;

          if (part.NamedRange != null)
          {
            namedRange = part.NamedRange.Expression.ToString();
          }

          reference = part.Reference.Expression.ToString();
        
          var contentControls =
              wordDocument.MainDocumentPart.Document.Body.Descendants()
              .Where(c => (c is SdtBlock || c is SdtRun || c is SdtCell) 
                && c.Descendants<Tag>().FirstOrDefault().Val.ToString() == reference)
              .Select(c => c.InnerText);
          if (contentControls.Count() > 0)
            value = contentControls.First().ToString();         

          contentParts.Add(part.Reference.Expression.ToString(), value);
        }
      }
    }

    //This version only allows Activities that inherit from ContentPart
    private void FillCellParts(MemoryStream memoryStream, string namedRange, string reference)
    {
      string value = string.Empty;
      contentParts = new Dictionary<string, string>();
    
      using (SpreadsheetDocument document = SpreadsheetDocument.Open(memoryStream, false))
      {         
        //Add ContentParts dropped onto DocumentParts
        foreach (Activity a in this.Activities)
        {
          ContentPart part = (ContentPart)a;
 
          namedRange = part.NamedRange.Expression.ToString();
          reference = part.Reference.Expression.ToString();

          // Find the sheet with the supplied name, and then use that Sheet object to retrieve a reference to the appropriate worksheet.
          Sheet sheet = document.WorkbookPart.Workbook.Descendants<Sheet>().Where(s => s.Name == namedRange).FirstOrDefault();

          if (sheet == null)
          {
            throw new ArgumentException("sheetName");
          }

          contentParts.Add(part.Reference.Expression.ToString(), GetCellValue(document, sheet.Id, reference));
        }
      }
    }

    private string GetCellValue(SpreadsheetDocument document, StringValue sheetid, string reference)
    {

      string value = string.Empty;

      // Retrieve a reference to the worksheet part, and then use its Worksheet property to get a cell reference
      WorksheetPart wsPart = (WorksheetPart)(document.WorkbookPart.GetPartById(sheetid));
      Cell cell = wsPart.Worksheet.Descendants<Cell>().Where(c => c.CellReference == reference).FirstOrDefault();

      // If the cell does not exist, return an empty string
      if (cell != null)
      {
        value = cell.InnerText;

        // If the cell represents a numeric value, you are done. For dates, this code returns the serialized value that 
        // represents the date. The code handles strings and Booleans individually. For shared strings, the code looks up the 
        // corresponding value in the shared string table. For Booleans, the code converts the value into the words TRUE or FALSE.
        if (cell.DataType != null)
        {
          switch (cell.DataType.Value)
          {
            case CellValues.SharedString:
              // For shared strings, look up the value in the shared strings table.
              var stringTable = document.WorkbookPart.GetPartsOfType<SharedStringTablePart>().FirstOrDefault();
              // If the shared string table is missing, something is wrong. Return the index that you found in the cell.
              // Otherwise, look up the correct text in the table.
              if (stringTable != null)
              {
                value = stringTable.SharedStringTable.
                  ElementAt(int.Parse(value)).InnerText;
              }
              break;

            case CellValues.Boolean:
              switch (value)
              {
                case "0":
                  value = "FALSE";
                  break;
                default:
                  value = "TRUE";
                  break;
              }
              break;
          }
        }
      }

      return value;
    }
  }
}
